
# 找K线右侧的坐标
# 这里会有在截图中再截图的操作, 如果开启了F12交易窗口, 则会报错, 关掉交易窗口就没事.

import cv2
import numpy as np


# 可能会有多个匹配, 返回所有匹配先
def find_all_matches(screen_img, template_img, threshold):
    """获取所有符合阈值的匹配结果，返回列表[(x, y, w, h, 匹配度)]"""
    # # 预处理（使用之前优化的预处理函数）
    # screen_processed = preprocess_image(screen_img)
    # template_processed = preprocess_image(template_img, is_template=True)
    h, w = template_img.shape[:2]
    
    if h == 0 or w == 0:
        return []
    
    # 执行模板匹配并获取匹配度矩阵
    result = cv2.matchTemplate(
        screen_img, 
        template_img, 
        cv2.TM_CCOEFF_NORMED
    )
    # 找到所有超过阈值的位置
    locations = np.where(result >= threshold)
    matches = []
    
    # 遍历所有匹配位置，记录坐标和匹配度
    for y, x in zip(locations[0], locations[1]):
        match_value = result[y, x]  # 该位置的匹配度（0-1）
        matches.append((x, y, w, h, match_value))  # 存储x坐标、y坐标、宽、高、匹配度
    return matches

# 找 模板匹配
def find_template_position(screen_img, template_img, threshold=0.85):
    """
    在屏幕截图中查找模板位置
    :param screen_img: 全屏截图（BGR格式）
    :param template_img: 模板图片（BGR格式）
    :param threshold: 匹配阈值（0-1，越高越严格） 数值为0.8时,还是会有错误, 比如需匹配0.00%,匹配到的却是0.05%
    :return: 匹配区域的左上角坐标(x, y)，若未找到返回None
    """
    # # 获取模板尺寸
    # h, w = template_img.shape[:2]
    
    # # 执行模板匹配
    # result = cv2.matchTemplate(screen_img, template_img, cv2.TM_CCOEFF_NORMED)
    
    # # 查找匹配度超过阈值的位置
    # locations = np.where(result >= threshold)

    # # 取匹配度最高的位置
    # if len(locations[0]) > 0:
    #     # 找到最大匹配值的索引
    #     print("#多个匹配的情况")
    #     max_idx = np.argmax(result)
    #     h_idx, w_idx = np.unravel_index(max_idx, result.shape)
    #     return (w_idx, h_idx, w, h)  # (x, y, 宽, 高)

    all_matches  = find_all_matches(screen_img, template_img,threshold)
    if not all_matches:
        return None  # 无匹配结果
    # 按X坐标升序排序（X越小越靠左），取第一个
    # 若X相同，可选匹配度最高的
    all_matches.sort(key=lambda m: (m[0], -m[1]))  # 优先按X排序，再按y排序 找到左上角的一个叉按钮
    rightmost = all_matches[0]
    return (rightmost[0], rightmost[1], rightmost[2], rightmost[3])  # 返回(x, y, w, h)
    # # 取第一个匹配位置（最相似的）
    # for pt in zip(*locations[::-1]):  # pt为左上角坐标
    #     print("#只有一个匹配的情况")
    #     return (pt[0], pt[1], w, h)  # 返回(x, y, 宽度, 高度)
    
    # return None  # 未找到匹配区域

def png_to_pixel_array(png_path):
    """
    将PNG图像转换为像素点数组（含alpha通道）
    :param png_path: PNG文件路径
    :return: 像素数组，shape为(height, width, 4)，每个元素是(B, G, R, A)
    """
    # 读取PNG图像（包含alpha通道）
    # cv2.IMREAD_UNCHANGED：保留所有通道（包括alpha）
    img = cv2.imread(png_path, cv2.IMREAD_UNCHANGED)
    
    if img is None:
        raise FileNotFoundError(f"无法读取PNG文件：{png_path}")
    
    # OpenCV默认读取为BGR格式，转换为BGRA（若原图有alpha通道）
    if img.shape[2] == 3:
        # 若PNG无alpha通道，添加全不透明的alpha通道
        img = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA)
    return img

# 加载PNG模板并转换为像素数组
template_array = png_to_pixel_array("eastmoney desktop software picture/template_top_left_button.png")


# 用于调试, 把图片存到硬盘上
def save_frame_to_image(frame, output_path):
    """
    将OpenCV格式的frame保存为图片文件
    :param frame: OpenCV处理后的图像（BGR格式，由cv2.cvtColor转换得到）
    :param output_path: 输出图片路径（需包含文件名和格式，如"stock_screenshot.png"）
    """
    if frame is None:
        raise ValueError("输入的frame为空，无法保存")
    
    # 校验图像格式（必须是2D或3D数组，符合图像格式）
    if len(frame.shape) not in (2, 3):
        raise ValueError(f"frame格式错误，应为图像数组，实际shape: {frame.shape}")
    
    # 使用OpenCV保存图片
    # 支持的格式由output_path的后缀决定（如.png、.jpg、.jpeg等）
    success = cv2.imwrite(output_path, frame)
    
    if not success:
        raise IOError(f"保存图片失败，请检查路径是否有效：{output_path}")
    print(f"图片已保存至：{output_path}")
    

def screen_region_to_pixel_array(x, y, width, height, screen_img):
    """
    从已有的股票软件截图中截取指定子区域，并转换为像素点数组（含alpha通道）
    :param x: 子区域左上角X坐标（相对于screen_img的局部坐标）
    :param y: 子区域左上角Y坐标（相对于screen_img的局部坐标）
    :param width: 子区域宽度
    :param height: 子区域高度
    :param screen_img: 已截取的股票软件全屏截图（BGR格式，OpenCV图像）
    :return: 子区域像素数组，shape为(height, width, 4)，每个元素是(B, G, R, A)
    """
    # 1. 校验输入截图有效性
    if screen_img is None or screen_img.size == 0:
        raise ValueError("输入的股票软件截图为空")
    
    # 2. 校验坐标是否在截图范围内（防止越界）
    screen_height, screen_width = screen_img.shape[:2]
    if (x < 0 or y < 0 or 
        x + width > screen_width or 
        y + height > screen_height):
        # save_frame_to_image(screen_img,"ERR 144.png")
        raise ValueError(
            f"截取区域超出截图范围！截图尺寸: {screen_width}×{screen_height}, "
            f"截取区域: ({x}, {y})-{x+width}×{y+height}"
        )
    
    # 3. 从全屏截图中截取子区域（BGR格式）
    sub_region = screen_img[y:y+height, x:x+width].copy()
    
    # 4. 转换为BGRA格式（添加alpha通道，默认完全不透明）
    if sub_region.shape[2] == 3:  # 若输入是BGR格式（无alpha）
        bgra_region = cv2.cvtColor(sub_region, cv2.COLOR_BGR2BGRA)
        bgra_region[:, :, 3] = 255  # alpha通道设为255（完全不透明）
    else:  # 若输入已含alpha通道（罕见）
        bgra_region = sub_region
    
    return bgra_region

def save_pixel_array_to_png(pixel_array, output_path):
    """
    将像素数组（BGRA格式）保存为PNG图片
    :param pixel_array: 由screen_region_to_pixel_array返回的像素数组（shape: (h, w, 4)）
    :param output_path: 输出PNG文件路径（如"sub_region.png"）
    """
    if pixel_array is None:
        raise ValueError("像素数组为空，无法保存")
    
    # 校验数组格式（必须是BGRA四通道）
    if len(pixel_array.shape) != 3 or pixel_array.shape[2] != 4:
        raise ValueError(f"像素数组格式错误，需为BGRA四通道，实际为{pixel_array.shape}")
    
    # 直接使用OpenCV保存（PNG格式会保留alpha通道）
    # cv2.imwrite默认支持BGRA格式保存为PNG
    success = cv2.imwrite(output_path, pixel_array)
    
    if not success:
        raise IOError(f"保存PNG失败，请检查路径是否有效：{output_path}")
    print(f"PNG图片已保存至：{output_path}")

def abs_safe(a,b):
    a_signed = a.astype(np.int16)
    b_signed = b.astype(np.int16)
    return abs(a_signed - b_signed)
    print(f"#181 {type(a)}\t{type(b)}")
    if (a >= 0) == (b >= 0):
        return abs(a - b)
    else:
        # Convert to float to avoid integer overflow?
        return float(abs(a)) + float(abs(b))
    
# 逐像素比对模板和屏幕区域
def compare_pixel_arrays(template_array, screen_array, alpha_threshold=128, color_tolerance=10):
    """
    逐像素比对模板和屏幕区域
    :param template_array: 模板像素数组（BGRA）
    :param screen_array: 屏幕区域像素数组（BGRA）
    :param alpha_threshold: 模板alpha通道阈值（高于此值视为不透明区域）
    :param color_tolerance: 颜色容差（允许的BGR值差异）
    :return: 匹配度（0-1），1表示完全匹配
    """
    # 校验尺寸是否一致
    if template_array.shape != screen_array.shape:
        raise ValueError(f"模板与屏幕区域尺寸不匹配：{template_array.shape} vs {screen_array.shape}")
    
    height, width = template_array.shape[:2]
    total_pixels = 0  # 参与比对的像素总数
    matched_pixels = 0  # 匹配的像素数
    
    # 逐像素比对
    for y in range(height):
        for x in range(width):

            # 获取模板像素的BGRA值
            t_b, t_g, t_r, t_a = template_array[y, x]

            # # 在像素点是灰色的情况下进行比对:
            # if 160 > t_b:
            #     continue
            # print("#159 t_b:",t_b,"\tt_g:",t_g,"\tt_r:",t_r)
            
            # 仅比对模板中不透明的区域（alpha高于阈值）
            if t_a < alpha_threshold:
                print("#165 透明 不去比对")
                continue
            
            # 获取屏幕区域对应像素的BGRA值
            s_b, s_g, s_r, _ = screen_array[y, x]
            # print("#159 xy:",x,",",y,"\t s_b:",s_b,"\ts_g:",s_g,"\ts_r:",s_r)
            # print("#159 xy:",x,",",y,"\t t_b:",t_b,"\tt_g:",t_g,"\tt_r:",t_r)
            
            # 计算BGR通道的差异（绝对值）
            b_diff = abs_safe(t_b,s_b)
            g_diff = abs_safe(t_g,s_g)
            r_diff = abs_safe(t_r,s_r)
            
            # 若所有通道差异均小于容差，视为匹配
            if b_diff <= color_tolerance and g_diff <= color_tolerance and r_diff <= color_tolerance:
                matched_pixels += 1
            
            total_pixels += 1
    
    # 计算匹配度（避免除零）
    if total_pixels == 0:
        return 0.0
    return matched_pixels / total_pixels

def find_top_left_of_k_line_area(screen_img):
    try:
        # 原函数逻辑
        # ...
        # 确认screen_img的类型
        if not isinstance(screen_img, np.ndarray):
            raise TypeError(f"预期ndarray类型，实际得到{type(screen_img)}")
    except Exception as e:
        # 详细记录错误信息，包括参数类型
        print(f"find_right_of_k_line_area执行失败: {str(e)}")
        print(f"输入参数类型: {type(screen_img)}")
        raise  # 重新抛出异常，让调用方知晓
    # 2. 加载模板图片
    template_img = cv2.imread("eastmoney desktop software picture/template_top_left_button.png")  # 模板路径
    if template_img is None:
        print("模板图片加载失败")
        return None
    # 3. 查找模板位置
    target_pos = find_template_position(screen_img, template_img)
    #self.text_region
    if target_pos:
        x, y, w, h = target_pos
        # print(f"找到目标区域：({x}, {y})，尺寸：{w}×{h}")
        return (x,y,w,h)
    else:
        # print("# 191 未找到目标区域")
        # 防范价格线涨停的情况:
        # 若能找到, 找到的结果向来是: 找到目标区域：(93, 124)，尺寸：19×19
        # 使用自主的比对方式来解决这一问题
        # 还需处理有一个公告栏的问题
        width_height = 19
        # screen_array = screen_region_to_pixel_array(93, 105, width_height, width_height,screen_img)
        pos_list = [
            (93, 104),
            (93, 105),
            (93, 124),
            (93, 125),
            (93, 126),
            (93, 127),
            ]
        # save_pixel_array_to_png(template_array, "out_test_template_array.png")
        # 3. 逐像素比对
        try:
            for coord in pos_list:
                x, y = coord
                screen_array = screen_region_to_pixel_array(x, y, width_height, width_height,screen_img)
                # print("#291")
                # save_pixel_array_to_png(screen_array, f"out_test_{y}.png")
                match_score = compare_pixel_arrays(
                    template_array, 
                    screen_array, 
                    alpha_threshold=128,  # 模板alpha>128的区域才参与比对
                    color_tolerance=15    # 颜色允许±15的差异
                )
                # print(f"匹配度：{match_score:.2f}")
                if match_score > 0.7:  # 匹配度超过90%视为成功
                    # print("模板匹配成功")
                    return (x, y,width_height,width_height)
                # else:
                #     screen_array = screen_region_to_pixel_array(93, 124, width_height, width_height,screen_img)
                #     match_score = compare_pixel_arrays(
                #         template_array, 
                #         screen_array, 
                #         alpha_threshold=128,  # 模板alpha>128的区域才参与比对
                #         color_tolerance=15    # 颜色允许±15的差异
                #     )
                #     if match_score > 0.9:  # 匹配度超过90%视为成功
                #         # print("模板匹配成功")
                #         return (93, 124,19,19)
            print(f"#299 执行到最后还没匹配 右上角的叉按钮\tmatch_score:{match_score}")

        except ValueError as e:
            print(f"# ERR 210 比对失败：{e}")

        return None
    
    




# 2025-08-31 这一次,试试threshold设置为.85 , 看会不会出问题. 因为所要识别的位置离左上右很近
#       事实证明是可以的.
# 

